package org.ihelpuoo.orm.handler.conn;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import org.ihelpuoo.db.ConnectionProxy;
import org.ihelpuoo.db.ConnectionWrapper;
import org.ihelpuoo.lang.ObjectUtils;
import org.ihelpuoo.orm.FieldRule;
import org.ihelpuoo.orm.IIIException;
import org.ihelpuoo.orm.IIIManual;
import org.ihelpuoo.orm.ObjectRule;
import org.ihelpuoo.orm.filter.Filter;
import org.ihelpuoo.orm.filter.FieldFilter;
import org.ihelpuoo.orm.handler.ObjectProcessor;
import org.ihelpuoo.orm.handler.FieldHandlers;
import org.ihelpuoo.orm.handler.LoopScope;
import org.ihelpuoo.util.Pager;

public class ConnectionProcessor extends ObjectProcessor
{

	private Logger			log	= Logger.getLogger(ConnectionProcessor.class);

	private FieldHandlers	delFieldHandlers;

	public ConnectionProcessor(IIIManual manual)
	{
		super(manual);
		this.setFieldHandlers = new FieldHandlers(new ConnSaveNormalFieldHandler(this),
				new ConnSaveListFieldHandler(this),new ConnSaveArrayFieldHandler(this),
				new ConnSaveMapFieldHandler(this),new ConnSaveObjectFieldHandler(this));
		this.delFieldHandlers = new FieldHandlers(new ConnDelNormalFieldHandler(this),
				new ConnDelListFieldHandler(this),new ConnDelArrayFieldHandler(this),
				new ConnDelMapFieldHandler(this),new ConnDelObjectFieldHandler(this));
	}

	@Override
	public boolean delete(ObjectRule r, Object src, Object obj, FieldFilter ignore)
			throws IIIException
	{
		r.checkDatabaseConfig((Connection)src,obj);
		try
		{
			// Delete all component fields
			if (r.isHasComponentField())
			{
				FieldRule[] rules = r.getComponentFields();
				for (int i = 0; i < rules.length; i++)
				{
					if (null != ignore && ignore.match(rules[i].getField()))
						continue;
					delFieldHandlers.process(r,rules[i].getField(),src,obj,null);
				}
			}
			// Delete self
			ConnectionProxy proxy = ConnectionWrapper.wrap((Connection)src);
			String sql = r.getDeleteSQL(obj);
			if (log.isDebugEnabled())
				log.debug(sql);
			return proxy.execute(sql);
		}
		catch (Exception e)
		{
			throw IIIException.wrap(e);
		}
	}

	@Override
	public int delete(ObjectRule r, Object src, List list, int off, int len) throws IIIException
	{
		if (null == list || list.size() == 0)
			return 0;
		r.checkDatabaseConfig((Connection)src,list.get(0));
		int count = 0;
		Iterator it = list.listIterator((off < 0 ? 0 : off));
		if (len <= 0)
			while (it.hasNext())
			{
				Object b = it.next();
				delete(r,src,b,null);
				count++;
			}
		else
			while (it.hasNext() && count < len)
			{
				Object b = it.next();
				delete(r,src,b,null);
				count++;
			}
		return count;
	}

	@Override
	public Object fetch(ObjectRule r, Object src, Object obj, FieldFilter actived,
			FieldFilter locked, String prefix) throws IIIException
	{
		r.checkDatabaseConfig((Connection)src,obj);
		String sql = r.getFetchSQL(obj,actived,locked);
		try
		{
			ConnectionProxy proxy = ConnectionWrapper.wrap((Connection)src);
			if (log.isDebugEnabled())
				log.debug(sql);
			ResultSet rs = proxy.query(sql);
			if (!rs.first())
				return null;
			manual.getResultSetProcessor().fetch(r,rs,obj,actived,locked,null);
			rs.close();
		}
		catch (Exception e)
		{
			throw IIIException.wrap(e);
		}
		return obj;
	}

	@Override
	public Object insert(ObjectRule r, Object src, Object obj, FieldFilter actived,
			FieldFilter locked) throws IIIException
	{
		r.checkDatabaseConfig((Connection)src,obj);
		try
		{
			preWriteObjectFields(r,src,obj,actived,locked);
			ConnectionProxy proxy = ConnectionWrapper.wrap((Connection)src);
			if (r.isSupportAutoIncrement())
			{
				if (proxy.getAutoCommit())
				{
					proxy.setAutoCommit(false);
					_processInsertwithSequence(obj,r,proxy,actived,locked);
					proxy.commit();
					proxy.setAutoCommit(true);
				}
				else
				{
					_processInsertwithSequence(obj,r,proxy,actived,locked);
				}
			}
			else
			{
				String sql = r.getInsertSQL(obj,actived,locked);
				if (log.isDebugEnabled())
					log.debug(sql);
				((ConnectionProxy)src).execute(sql);
			}
			afterWriteObjectFields(r,src,obj,actived,locked);
			return obj;
		}
		catch (Exception e)
		{
			throw IIIException.wrap(e);
		}

	}

	private void preWriteObjectFields(ObjectRule r, Object src, Object obj, FieldFilter actived,
			FieldFilter locked) throws IIIException
	{
		if (r.isHasComponentField())
		{
			FieldRule[] cfs = r.getComponentFields();
			for (int i = 0; i < cfs.length; i++)
			{
				FieldRule fr = cfs[i];
				if (!fr.isObject())
					continue;
				// current object should be treat before sub object
				if (fr.isIamFirst())
					continue;
				if (fr.isIgnore(actived,locked))
					continue;

				setFieldHandlers.process(r,fr.getField(),src,obj,null);

				/*
				 *  When sub object done for insert, try to set the relative value 
				 */
				if (fr.hasRelation())
				{
					Object sub = ObjectUtils.getFieldValue(obj,fr.getField());
					try
					{
						Method setter = ObjectUtils.getSetter(r.getType(),fr.getMe());
						Object v = ObjectUtils.getFieldValue(sub,fr.getThat());
						setter.invoke(obj,v);
					}
					catch (Exception e)
					{
						throw new IIIException(e);
					}
				}

			}
		}
	}

	private void afterWriteObjectFields(ObjectRule r, Object src, Object obj, FieldFilter actived,
			FieldFilter locked) throws IIIException
	{
		if (r.isHasComponentField())
		{
			FieldRule[] cfs = r.getComponentFields();
			for (int i = 0; i < cfs.length; i++)
			{
				FieldRule fr = cfs[i];
				// current object should be treat after sub object
				if (!fr.isIamFirst())
					continue;
				if (fr.isIgnore(actived,locked))
					continue;

				if (fr.hasRelation())
					try
					{
						Object v = ObjectUtils.getFieldValue(obj,fr.getMe());
						Method setter = ObjectUtils.getSetter(fr.getComponentType(),fr.getThat());
						// for object
						if (fr.isObject())
						{
							// try to set the value to sub obj
							Object sub = ObjectUtils.getFieldValue(obj,fr.getField());
							if (null != sub)
								setter.invoke(sub,v);
						}
						// for array
						else if (fr.isArray())
						{
							Object[] subs = (Object[])ObjectUtils.getFieldValue(obj,fr.getField());
							if (null != subs)
								for (int z = 0; z < subs.length; z++)
									setter.invoke(subs[z],v);
						}
						// for list
						else if (fr.isList())
						{
							List subs = (List)ObjectUtils.getFieldValue(obj,fr.getField());
							Iterator it = subs.iterator();
							while (it.hasNext())
							{
								Object sub = it.next();
								setter.invoke(sub,v);
							}

						}
						// for map
						else if (fr.isMap())
						{
							Map subs = (Map)ObjectUtils.getFieldValue(obj,fr.getField());
							Iterator it = subs.values().iterator();
							while (it.hasNext())
							{
								Object sub = it.next();
								setter.invoke(sub,v);
							}
						}
					}
					catch (Exception e)
					{
						throw new IIIException(e);
					}

				setFieldHandlers.process(r,fr.getField(),src,obj,null);
			}
		}
	}

	private void _processInsertwithSequence(Object obj, ObjectRule r, ConnectionProxy proxy,
			FieldFilter actived, FieldFilter locked) throws Exception
	{
		String sql = r.getInsertSQL(obj,actived,locked);
		if (log.isDebugEnabled())
			log.debug(sql);
		proxy.execute(sql);
		Class type = obj.getClass();
		if (r.isSupportAutoIncrement())
		{
			sql = r.getAutoIncrementsSQL(obj);
			if (log.isDebugEnabled())
				log.debug(sql);
			ResultSet rs = proxy.query(sql);
			if (!rs.first())
				throw new SQLException("Can not fetch the auoto_increment fields max value by \""
						+ sql + "\"");
			FieldRule[] ais = r.getAutoIncrements();
			for (int i = 0; i < ais.length; i++)
			{
				Object v = rs.getObject(ais[i].getNameInDB());
				if (null == v)
				{
					throw new SQLException("Error happened when set field \"" + ais[i].getName()
							+ "\" by  \"" + sql + "\"");
				}
				v = ObjectUtils.cast(ais[i].getField().getType(),v);
				Method setter = ObjectUtils.getSetter(type,ais[i].getField());
				setter.invoke(obj,new Object[]{v});
			}
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public List query(ObjectRule r, Object src, Class type, Filter filter, FieldFilter actived,
			FieldFilter locked, Pager pager, Object refer) throws IIIException
	{
		r.checkDatabaseConfig((Connection)src,refer);
		List list = new LinkedList();
		String sql = r.getQuerySQL(filter,actived,locked,refer);
		try
		{
			ConnectionProxy proxy = ConnectionWrapper.wrap((Connection)src);
			if (log.isDebugEnabled())
				log.debug(sql);
			ResultSet rs = proxy.query(sql);
			if (rs.last())
			{
				if (null != pager)
					if (pager.getPageSize() > 1000)
						rs.setFetchSize(20);
					else
						rs.setFetchSize(pager.getPageSize());
				LoopScope ls = LoopScope.evaluate(pager,rs.getRow());
				if (rs.absolute(ls.start + 1))
					for (int i = ls.start; i < ls.max; i++)
					{
						Object o = ObjectUtils.newInstance(type);
						manual.getResultSetProcessor().fetch(r,rs,o,actived,locked,null);
						list.add(o);
						if (!rs.next())
							break;
					}
			}
			rs.close();
		}
		catch (Exception e)
		{
			throw IIIException.wrap(e);
		}
		return list;
	}

	@Override
	public Object update(ObjectRule r, Object src, Object obj, FieldFilter actived,
			FieldFilter locked) throws IIIException
	{
		r.checkDatabaseConfig((Connection)src,obj);
		try
		{
			preWriteObjectFields(r,src,obj,actived,locked);
			ConnectionProxy proxy = ConnectionWrapper.wrap((Connection)src);
			String sql = r.getUpdateSQL(obj,actived,locked);
			if (log.isDebugEnabled())
				log.debug(sql);
			proxy.execute(sql);
			afterWriteObjectFields(r,src,obj,actived,locked);
			return obj;
		}
		catch (Exception e)
		{
			throw IIIException.wrap(e);
		}
	}

}
